Notes on React

Resource

在设计 API 时,我们刻意隐藏了实现细节。作为一名 React 开发者,你只需要关注视图是什么样子,然后由 React 来处理如何来实现,所以我们不需要 React 开发者了解并发的底层运行原理。React v18.0 – React 中文文档

Pull requests · reactjs/rfcs · GitHub

组件

props

props 是组件的属性,通常用于从父组件向子组件传递数据。默认情况下,props 是只读的,这意味着子组件无法直接修改其接收到的 props。这种设计有助于保持数据流的单向性,使得组件的状态更加可控和可预测。

如果子组件需要修改 props 传递的数据,通常的做法是让父组件传递一个回调函数给子组件,在子组件内部调用这个回调函数来请求数据更改。然后,父组件可以相应地更新其状态,并重新渲染子组件,以传递更新后的 props。

Hooks

useState

你不能像这样把函数放入状态:

const [fn, setFn] = useState(someFunction);
function handleClick() {  setFn(someOtherFunction);}

因为你传递了一个函数,React 认为 someFunction 是一个 初始化函数,而 someOtherFunction 是一个 更新函数,于是它尝试调用它们并存储结果。要实际 存储 一个函数,你必须在两种情况下在它们之前加上 () =>。然后 React 将存储你传递的函数。

初始化函数

如果将函数传递给 useState,React 仅在初始化期间调用它。

更新函数

它获取待定状态并从中计算下一个状态。

如果在同一事件中进行多个更新,则更新函数可能会有帮助。如果访问状态变量本身不方便(在优化重新渲染时可能会遇到这种情况),它们也很有用。

存储上一次的信息

useState – React 中文文档

在极少数情况下,你可能希望在响应渲染时调整状态——例如,当 props 改变时,你可能希望改变状态变量。

useEffect

在 Effect 中使用 fetch请求数据的一种流行方式,特别是在完全的客户端应用程序中。然而,这是一种非常手动的方法,而且有很大的缺点:

  • Effect 不在服务器上运行。这意味着初始服务器渲染的 HTML 将只包含没有数据的 loading 状态。客户端电脑仅为了发现它现在需要加载数据,将不得不下载所有的脚本来渲染你的应用程序。这并不高效。
  • 在 Effect 中直接请求数据很容易导致“网络瀑布”。当你渲染父组件时,它会请求一些数据,再渲染子组件,然后重复这样的过程来请求子组件的数据。如果网络不是很快,这将比并行请求所有数据要慢得多。
  • 在 Effect 中直接请求数据通常意味着你不会预加载或缓存数据。例如,如果组件卸载后重新挂载,它不得不再次请求数据。
  • 这不符合工效学。在调用 fetch 时,需要编写大量样板代码,以避免像 竞争条件 这样的 bug。

依赖项为空数组的 Effect 不会在组件任何的 props 或 state 发生改变时重新运行。

useRef

useRef – React 中文文档

使用 ref 可以确保:

  • 可以在重新渲染之间 存储信息(普通对象存储的值每次渲染都会重置)。
  • 改变它 不会触发重新渲染(状态变量会触发重新渲染)。
  • 对于组件的每个副本而言,这些信息都是本地的(外部变量则是共享的)。

ref 不适合用于存储期望显示在屏幕上的信息。如有需要,使用 state 代替。

可以在 事件处理程序或者 Effect 中读取和写入 ref。

通常情况下,在渲染过程中写入或读取 ref.current不允许的。然而,在这种情况下是可以的,因为结果总是一样的,而且条件只在初始化时执行,所以是完全可预测的。

useCallback

useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。

useCallback 不会阻止创建函数。你总是在创建一个函数(这很好!),但是如果没有任何东西改变,React 会忽略它并返回缓存的函数。

使用 useCallback 缓存函数仅在少数情况下有意义:

  • 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。
  • 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 useCallback 中的函数依赖于它,或者依赖于 useEffect 中的函数。
useCallbackmemo

useMemo

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。

useMemo 在多次重新渲染中缓存了函数计算的结果直到依赖项的值发生变化。

使用 useMemo 进行优化仅在少数情况下有价值:

  • 你在 useMemo 中进行的计算明显很慢,而且它的依赖关系很少改变。
  • 将计算结果作为 props 传递给包裹在 memo 中的组件。当计算结果没有改变时,你会想跳过重新渲染。记忆化让组件仅在依赖项不同时才重新渲染。
  • 你传递的值稍后用作某些 Hook 的依赖项。例如,也许另一个 useMemo 计算值依赖它,或者 useEffect 依赖这个值。

第二点本质上就是 useCallback

useCallback 和 useMemo

useCallback – React 中文文档

  • 联系:

    • 二者都接收一个依赖项数组,用于决定何时重新计算或重新创建。
    • 二者都用于性能优化,避免不必要的重复计算或重新渲染。
  • 区别:

    • useCallback 返回一个 memoized 的回调函数,用于缓存函数引用。
    • useMemo 返回一个 memoized 的值,用于缓存计算结果。

useContext

Usage

HooksUsage
useState – React 中文文档
5.2 M
useEffect – React 中文文档
3.2 M
useRef – React 中文文档
1.1 M
useCallback – React 中文文档
877 K
useMemo – React 中文文档
774 K
useContext – React 中文文档
692 K
useReducer – React 中文文档
62 K
useLayoutEffect – React
55 K
useId – React 中文文档35.6 K
useTransition – React 中文文档
29.4 K
useImperativeHandle – React
26.6 K
useDeferredValue – React 中文文档1.7K

Components

Suspense

<Suspense> lets you display a fallback until its children have finished loading.

<Suspense> – React 中文文档

API

memo

memo – React 中文文档

memo(Component, arePropsEqual?)

使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证。

参数

可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。

这个比较函数似乎与 arr.sort(fn)

返回值

memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化。

应用

即使使用 memo,如果它自己的 state 或正在使用的 context 发生更改,组件也会重新渲染

如果每次父组件重新渲染时创建一个新的对象或数组,即使它们每个元素都相同,React 仍会认为它已更改。同样地,如果在渲染父组件时创建一个新的函数,即使该函数具有相同的定义,React 也会认为它已更改。

如果 props 是一个对象,可以使用 useMemo 避免父组件每次都重新创建该对象

memo 和 useCallback

虽然 memouseCallback 都可以用于优化组件的性能,但它们的作用略有不同,适用于不同的场景。

  1. React. memo:
    • React.memo 是一个高阶组件,用于包裹函数组件并缓存其渲染结果。它可以将组件的 props 进行浅比较,并在 props 没有变化时阻止不必要的重新渲染。
    • 当父组件重新渲染时,React. memo 会比较当前组件的 props 和上一次渲染时的 props。如果两者相等,则 React. memo 会跳过当前组件的重新渲染,直接返回上一次的渲染结果。
    • React. memo 适用于函数组件的渲染结果会完全根据其 props 决定的情况,这样可以避免在 props 没有变化时进行不必要的重新渲染。
const MemoizedComponent = React.memo(MyComponent);
  1. useCallback:
    • useCallback 用于缓存函数,它接收一个函数和一个依赖项数组,并返回一个 memoized 的函数。它的主要作用是在依赖项未变化时返回上一次缓存的函数引用,从而避免在父组件重新渲染时重新创建函数。
    • 当父组件重新渲染时,由于函数引用没有发生变化,子组件可以避免不必要的重新渲染,因为它们使用的是相同的回调函数引用。
const memoizedCallback = useCallback(() => { // Callback logic }, [/* 依赖项数组 */]);

总结:

  • React.memo 适用于函数组件的渲染结果完全由 props 决定的情况,可以帮助组件在 props 没有变化时跳过重新渲染。
  • useCallback 适用于缓存函数,可以确保在依赖项未变化时返回相同的函数引用,从而避免在父组件重新渲染时重新创建函数。

在某些情况下,你可能需要同时使用 React.memouseCallback 来最大程度地优化组件的性能。

在某些情况下,如果 props 中包含了引用类型(如对象或数组),浅比较可能会导致不准确的结果。这时候,你可能需要手动使用 useMemouseCallback 来优化性能,确保正确比较引用类型的变化。但对于大多数情况,React.memo 可以很好地帮助你避免不必要的重新渲染,而无需手动指定依赖项数组。

forwardRef

forwardRef – React 中文文档

默认情况下,每个组件的 DOM 节点都是私有的。然而,有时候将 DOM 节点公开给父组件是很有用的

  • 使用 forwardRef 包裹需要公开 DOM 的子组件, 使其能够接受 ref 参数, 并在子组件的内部某个节点绑定 DOMref
  • 父组件使用 useRef 声明一个 nullref ,然后将 ref 传递给子组件
  • 子组件将接受到的 ref 转发 forward 给子组件内容绑定了 ref 的某个 DOM 节点/标签
  • 父组件可以访问子组件内部某个 DOM 节点并对其调用方法, 例如 focus()

将组件内部的 ref 暴露给 DOM 节点会使得在稍后更改组件内部更加困难。通常会暴露可重用的低级组件的 DOM 节点,例如按钮或文本输入框,但不会在应用程序级别的组件中这样做,例如头像或评论。

lazy

lazy(load)
参数
  • load

一个返回 Promise 或另一个 thenable(具有 then 方法的类 Promise 对象)的函数。

返回的 Promise 和 Promise 的解析值都将被缓存,因此 React 不会多次调用 load 函数。如果 Promise 被拒绝,则 React 将抛出拒绝原因给最近的错误边界处理。

返回值

你需要返回一个 Promise 或其他 thenable(具有 then 方法的类 Promise 对象)。它最终需要解析为有效的 React 组件类型,例如函数、memo 或 forwardRef 组件。

Usage

APIUsage
memo889 k
forwardRef327 k
createContext147 k
lazy125 k
startTransition8 k

渲染

条件渲染

事件处理

#Todo react props 只读不改

高阶组件